#include "dswvideosession_local_client.h"

static const ST_P2PMSG_T m_lanvideomsg[] = {
	{"MSG_P2P_REPORTID_REQ", "", CDswvideosession_local_client::cbDealReportIDReq},
	{"END","", NULL},
};

CDswvideosession_local_client::CDswvideosession_local_client()
{
}

CDswvideosession_local_client::~CDswvideosession_local_client()
{
	CLEAR_EV(m_nCheckDiskTask);
	while(!m_video_mem.vlist.empty()){
		LPST_VIDEO_FRAME_T pNode = m_video_mem.vlist.front();
		m_video_mem.vlist.pop_front();
		if (pNode->pFrame != NULL)
			delete pNode->pFrame;
		delete pNode;
	}
	pthread_mutex_destroy(&m_video_mem.lock);
	pthread_mutex_destroy(&m_nRecoderLock);

	ST_SESSION_NOTIFY_T nNotify;
	nNotify.nCmd = EXT_SESSION_REMOVE;
	nNotify.pKey = m_sPUAID;
	nNotify.pParam = (void*)this;
	m_pNotifyFunc(&nNotify, m_pParent);
	LOG4CPLUS_DEBUG(log_dsw, "Destroy CDswvideosession_local_client.");
}

CDswvideosession_local_client* CDswvideosession_local_client::createNew(LPST_ENV_T pEnv, char* pHost, int nPort, char* pPUAID, CB_SESSION_NOTIFY pNotifyFunc, void* pCaller, int nExChId)
{
	struct timeval tv = {30, 0};
	struct bufferevent *pBev = bufferevent_socket_new(pEnv->pEvBase, -1, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_THREADSAFE);
	bufferevent_set_timeouts(pBev, &tv, NULL);
	return new CDswvideosession_local_client(pEnv, pBev, sizeof(short) + 1, pHost, nPort, pPUAID, pNotifyFunc, pCaller, nExChId);
}

CDswvideosession_local_client::CDswvideosession_local_client(LPST_ENV_T pEnv, struct bufferevent * pBev, int nHeadSize, char* pHost, int nPort, char* pPUAID, CB_SESSION_NOTIFY pNotifyFunc, void* pCaller, int nExChId)
: CTcpcombase(pEnv, pBev, nHeadSize, pHost, nPort)
, m_pNotifyFunc(pNotifyFunc)
, m_pParent(pCaller)
, m_pRecoder(NULL)
, m_nHdStatus((ENM_HD_STATUS) -1)
, m_nExChId(nExChId)
, m_nStartTm(0)
{
	strcpy(m_sHost, pHost);
	m_nPort = nPort;
	strcpy(m_sPUAID, pPUAID);
	pthread_mutex_init(&m_video_mem.lock, NULL);
	pthread_mutex_init(&m_nRecoderLock, NULL);
	m_video_mem.seq = 0;
	m_nCheckDiskTask.ev = NULL;
	LOG4CPLUS_DEBUG(log_dsw, "Create CDswvideosession_local_client [ " << pPUAID << "-" << pHost << ":" << nPort << " ]");
}

int CDswvideosession_local_client::MakePayloadLength(char* pData, int nLen)
{
	if (m_nHeadSize == sizeof(short) + 1){
		return (pData[2] << 8 | pData[1] + 1);
	}
	else{
		return (pData[3] << 24 | pData[2] << 16 | pData[1] << 8 | pData[0]);
	}
}

bool CDswvideosession_local_client::ProcessMessage(char* pMsg, int nLen, struct evbuffer *outbuf)
{
	if (m_nHeadSize == 3){
		CMarkup xml;
		xml.SetDoc(pMsg);
		if(!xml.FindElem("Message"))
			return false;
		xml.IntoElem();
		if(!xml.FindElem("DSW_HEADER"))
			return false;
		xml.IntoElem();
		if(!xml.FindElem("MessageType"))
			return false;
		char smsgreq[64];
		smsgreq[0] = '\0';
		strcpy(smsgreq, xml.GetData().c_str());
		xml.OutOfElem();
		LPST_P2PMSG_T pPos = (LPST_P2PMSG_T)m_lanvideomsg;
		while(strcmp(pPos->sMsgReq, "END") != 0){
			if ((strcmp(pPos->sMsgReq, smsgreq) == 0) && (pPos->pP2PFunc != NULL)){
				return pPos->pP2PFunc(pMsg, pPos->sMsgResp, this);
				break;
			}
			pPos ++;
		}
	}
	else{
		if (abs(time(NULL) - m_nStartTm) > 30){
			CMarkup xml;
			Make_ACKReq(xml);
			SendMsg((char*)xml.GetDoc().c_str(), strlen(xml.GetDoc().c_str()));
		}
		if (memcmp(pMsg, LIVE_V_FLAG, LIVE_V_FLAG_SIZE) == 0)
			return ProcessVideoMsg(pMsg, nLen);
		else if (memcmp(pMsg, LIVE_A_FLAG, LIVE_A_FLAG_SIZE) == 0)
			return true;
	}
	return false;
}

void CDswvideosession_local_client::OnConnect(struct bufferevent * bev, short events)
{
	struct timeval tv_w = {10, 0};
	struct timeval tv_r = {30, 0};
	bufferevent_set_timeouts(m_pBev, &tv_r, &tv_w);
	if (m_nCheckDiskTask.ev == NULL){
		m_nCheckDiskTask.ev = evtimer_new(m_pEnv->pEvBase, CDswvideosession_local_client::cbCheckDiskTask, this);
		m_nCheckDiskTask.tv.tv_sec = 1;
		m_nCheckDiskTask.tv.tv_usec = 0;
		evtimer_add(m_nCheckDiskTask.ev, &m_nCheckDiskTask.tv);
	}
}

void CDswvideosession_local_client::OnError(EN_ERR_TYPE nType, struct bufferevent * bev, short events)
{
	ST_SESSION_NOTIFY_T nNotify;
	nNotify.nCmd = EXT_SESSION_REMOVE_RECONNECT;
	nNotify.pKey = m_sPUAID;
	nNotify.pParam = (void*)this;
	m_pNotifyFunc(&nNotify, m_pParent);
	StopRecoder();
	delete this;
}

void CDswvideosession_local_client::Make_ACKReq(CMarkup &xml)
{
	xml.SetDoc("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n");
	xml.AddElem("Message");
	xml.AddAttrib("Version", "1.0.0");
	xml.IntoElem();
		xml.AddElem("DSW_HEADER");
		xml.IntoElem();
			xml.AddElem("MessageType", "MSG_P2P_ACK_REQ");
		xml.OutOfElem();
		xml.AddElem("PUC_ID", m_sPUCID);
		xml.AddElem("PUA_ID", m_sPUAID);
		xml.AddElem("CHN", 0);
		xml.AddElem("DSW_CHN", 0);
		xml.AddElem("STREAM", 1);
		xml.AddElem("DSW_STREAM", 1);
	xml.OutOfElem();
}

void CDswvideosession_local_client::Make_ReportIDResp(CMarkup &xml)
{
	sprintf(m_sPUCID, "LAN_%d", time(NULL));
	xml.SetDoc("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n");
	xml.AddElem("Message");
	xml.AddAttrib("Version", "1.0.0");
	xml.IntoElem();
		xml.AddElem("DSW_HEADER");
		xml.IntoElem();
			xml.AddElem("MessageType", "MSG_P2P_REPORTID_RESP");
		xml.OutOfElem();
		xml.AddElem("DSW_RESULT");
		xml.IntoElem();
			xml.AddElem("ErrorCode", 0);
			xml.AddElem("PUC_ID", m_sPUCID);
			xml.AddElem("CHN", 0);
			xml.AddElem("STREAM", 1);
		xml.OutOfElem();
	xml.OutOfElem();
}

void CDswvideosession_local_client::SendMsg(char* pData, int nLen)
{
	char sHead[32];
	char* pPos = pData;
	short nLeft = nLen;

	sHead[0] = 0x00;
	memcpy(sHead + 1, &nLen, sizeof(short));
	evbuffer_add(bufferevent_get_output(m_pBev), sHead, 3);
	evbuffer_add(bufferevent_get_output(m_pBev), pData, nLen);
	evbuffer_add(bufferevent_get_output(m_pBev), sHead, 1);
}

bool CDswvideosession_local_client::DealReportIDReq(void* pMsgReq, const char* pMsgResp)
{
	CMarkup xml;
	char sPUAID[64];
	
	xml.SetDoc((char*)pMsgReq);
	if (!xml.FindElem("Message"))
		return false;
	xml.IntoElem();
		if (!xml.FindElem("PUA_ID"))
			return false;
		strcpy(sPUAID, xml.GetData().c_str());
		if (strcmp(sPUAID, m_sPUAID) == 0){
			CMarkup xml;
			Make_ReportIDResp(xml);
			SendMsg((char*)xml.GetDoc().c_str(), strlen(xml.GetDoc().c_str()));
			m_nHeadSize = sizeof(int);
			return true;
		}
	xml.OutOfElem();
	return false;
}

bool CDswvideosession_local_client::ProcessVideoMsg(char* pMsg, int nLen)
{
	LP_TcpStreamHead_V pTcpStream = (LP_TcpStreamHead_V)pMsg;
	char* pVideoData = pMsg + sizeof(TcpStreamHead_V);

	pthread_mutex_lock(&m_video_mem.lock);
	LPST_VIDEO_FRAME_T pNode;
	if (m_video_mem.vlist.size() < MAX_FRAMES){
		pNode = new ST_VIDEO_FRAME_T;
		pNode->pFrame = new unsigned char[pTcpStream->streamsize + (pTcpStream->streamsize % sizeof(int))];
		pNode->bufsize = pTcpStream->streamsize;
	}
	else{
		pNode = m_video_mem.vlist.front();
		m_video_mem.vlist.pop_front();
		if (pNode->bufsize < pTcpStream->streamsize){
			delete pNode->pFrame;
			pNode->pFrame = new unsigned char[pTcpStream->streamsize + (pTcpStream->streamsize % sizeof(int))];
			pNode->bufsize = pTcpStream->streamsize;
		}
	}
	pNode->is_keyframe = (memcmp(pVideoData, "\x00\x00\x00\x01\x67", 5) == 0);
	pNode->seq = m_video_mem.seq++;
	pNode->length = pTcpStream->streamsize;
	pNode->ch_num = m_nExChId;
	pNode->sub_num = 0;
	gettimeofday(&pNode->timestamp, NULL);
	pNode->is_newbs = false;
	memcpy(pNode->pFrame, pVideoData, pTcpStream->streamsize);
	m_video_mem.vlist.push_back(pNode);
	pthread_mutex_unlock(&m_video_mem.lock);

	bool bStartRec = false;
	pthread_mutex_lock(&m_nRecoderLock);
	bStartRec = (m_pRecoder != NULL);
	pthread_mutex_unlock(&m_nRecoderLock);
	if (!bStartRec)
		StartRecoder(pTcpStream->width, pTcpStream->height, pTcpStream->fps);
	return true;
}

void CDswvideosession_local_client::GetFrameFromMem(LPST_VIDEO_FRAME_T pFrame)
{
	bool bHaveFrame = false;
	int nstream = pFrame->sub_num;
	unsigned int nseq = pFrame->seq;
	pthread_mutex_t* pmutex = &(m_video_mem.lock);
	LIST_VIDEO_MEM_T* plist = &(m_video_mem.vlist);

	pFrame->length = -1;
	pthread_mutex_lock(pmutex);
	if (plist->empty()){
		pthread_mutex_unlock(pmutex);
		return;
	}
	LIST_VIDEO_MEM_T::iterator it;
	for (it = plist->begin(); it != plist->end(); it++){
		if ((*it)->seq >= nseq){
			if ((nseq == 0) && !(*it)->is_keyframe){
				continue;
			}
			if ((*it)->length > pFrame->bufsize){
				if (pFrame->pFrame != NULL)
					delete pFrame->pFrame;
				pFrame->pFrame = new unsigned char[(*it)->length + ((*it)->length % sizeof(int))];
				memcpy(pFrame, (*it), sizeof(ST_VIDEO_FRAME_T) - sizeof(unsigned char*));
				pFrame->bufsize = (*it)->length;
			}
			else{
				int nsize = pFrame->bufsize;
				memcpy(pFrame, (*it), sizeof(ST_VIDEO_FRAME_T) - sizeof(unsigned char*));
				pFrame->bufsize = nsize;
			}
			memcpy(pFrame->pFrame, (*it)->pFrame, (*it)->length);
			bHaveFrame = true;
			break;
		}
	}
	pthread_mutex_unlock(pmutex);
}

void CDswvideosession_local_client::SetDiskStatus(ENM_HD_STATUS nStatus)
{
	pthread_mutex_lock(&m_nRecoderLock);
	m_nHdStatus = nStatus;
	if (m_pRecoder != NULL){
		m_pRecoder->SetDiskStatus(nStatus);
	}
	pthread_mutex_unlock(&m_nRecoderLock);
}

void CDswvideosession_local_client::ForceSave()
{
	pthread_mutex_lock(&m_nRecoderLock);
	if (m_pRecoder != NULL){
		m_pRecoder->ForceSave();
	}
	pthread_mutex_unlock(&m_nRecoderLock);
}

void CDswvideosession_local_client::StartRecoder(int nWidth, int nHeight, int nFps)
{
	pthread_mutex_lock(&m_nRecoderLock);
	if (m_pRecoder == NULL){
		char sBuf[128] = {0};
		sprintf(sBuf, "exch%02d", m_nExChId);
		ST_EXTERNAL_CAM_PROPERTY_T nProperty;
		nProperty.nWidth = nWidth;
		nProperty.nHeight = nHeight;
		nProperty.nFps = nFps;
		m_pRecoder = new CDswonvifrecoder(m_pEnv, sBuf, &m_video_mem, &nProperty, m_nHdStatus);
	}
	pthread_mutex_unlock(&m_nRecoderLock);
}

void CDswvideosession_local_client::StopRecoder()
{
	pthread_mutex_lock(&m_nRecoderLock);
	if (m_pRecoder){
		delete m_pRecoder;
		m_pRecoder = NULL;
	}
	pthread_mutex_unlock(&m_nRecoderLock);
}

void CDswvideosession_local_client::CheckDiskTask(int fd, short events)
{
	ENM_HD_STATUS nStatus;
	void* pParam = (void*)&nStatus;
	m_pEnv->pOnNotifyFunc(PMSG_GET_DISKSTATE, pParam);
	SetDiskStatus(nStatus);
	m_nCheckDiskTask.tv.tv_sec = 10;
	m_nCheckDiskTask.tv.tv_usec = 0;
	evtimer_add(m_nCheckDiskTask.ev, &m_nCheckDiskTask.tv);
}

